import { In, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm';
import { BaseRepository } from './base.repository';
import { BaseTreeRepository } from './tree.repository';
import {
    PaginateOptions,
    QueryHook,
    ServiceListQueryOption,
} from 'src/config/types';
import { SelectTrashMode, TreeChildrenResolve } from 'src/config/defaultconfig';
import { manualPaginate, paginate } from '../helpers';
import { isNil } from 'lodash';
import { ForbiddenException, NotFoundException } from '@nestjs/common';
export abstract class BaseService<
    E extends ObjectLiteral,
    R extends BaseRepository<E> | BaseTreeRepository<E>,
    P extends ServiceListQueryOption<E> = ServiceListQueryOption<E>,
> {
    /* 存储的仓库 */
    protected repository: R;

    /* 
       是否开启 软删除
      */
    protected enableTrash = false;
    constructor(repository: R) {
        this.repository = repository;

        if (
            !(
                this.repository instanceof BaseRepository ||
                this.repository instanceof BaseTreeRepository
            )
        ) {
            throw new Error(
                'Repository must instance of BaseRepository or BaseTreeRepository in DataService!',
            );
        }
    }
    //获取数据列表
    async list(option?: P, callback?: QueryHook<E>) {
        const { trashed: isTrashed = false } = option ?? {};
        const trashed = isTrashed || SelectTrashMode.NONE;
        if (this.repository instanceof BaseTreeRepository) {
            const withTrashed =
                this.enableTrash &&
                (trashed === SelectTrashMode.ALL || trashed == SelectTrashMode.ONLY);
            const onlyTrashed =
                this.enableTrash &&
                (trashed == SelectTrashMode.ALL || trashed == SelectTrashMode.ONLY);
            const tree = await this.repository.findTrees({
                ...option,
                withTrashed,
                onlyTrashed,
            });
        }
        const qb = await this.buildListQB(
            this.repository.buildBaseQB(),
            option,
            callback,
        );
        return qb.getMany();
    }
    buildListQB(qb: SelectQueryBuilder<E>, options?: P, callback?: QueryHook<E>) {
        const { trashed } = options ?? {};
        const queryName = this.repository.qbName;
        //是否查询回收站
        if (
            this.enableTrash &&
            (trashed == SelectTrashMode.ALL || trashed == SelectTrashMode.ONLY)
        ) {
            qb.withDeleted();
            if (trashed === SelectTrashMode.ONLY) {
                qb.where(`${queryName}.deletedAt is not null`);
            }
            if (callback) return callback(qb);
            return qb;
        }
    }

    //获取分页数据
    async paginate(options?: PaginateOptions & P, callback?: QueryHook<E>) {
        const queryOptions = (options ?? {}) as P;
        if (this.repository instanceof BaseRepository) {
            const data = await this.list(queryOptions, callback);
            return manualPaginate(options, data);
        }
        const qb = await this.buildListQB(
            this.repository.buildBaseQB(),
            queryOptions,
            callback,
        );
        return paginate(qb, options);
    }

    async detail(id: string, callback: QueryHook<E>) {
        const qb = await this.buildItemQB(
            id,
            this.repository.buildBaseQB(),
            callback,
        );
        const item = qb.getOne();
        if (!item)
            throw new NotFoundException(
                `${this.repository.qbName} ${id} not exists!`,
            );
        return item;
    }

    protected async buildItemQB(
        id: string,
        qb: SelectQueryBuilder<E>,
        callback?: QueryHook<E>,
    ) {
        qb.where(`${this.repository.qbName}.id=:id`, { id });
        if (callback) callback(qb);
        return qb;
    }

    async delete(ids: string[], trash?: boolean) {
        let items: E[] = [];
        if (this.repository instanceof BaseTreeRepository) {
            items = await this.repository.find({
                where: { id: In(ids) as any },
                withDeleted: this.enableTrash ? true : undefined,
                relations: ['parent', 'children'],
            });
            //对子类也删除
            if (this.repository.childrenResolve === TreeChildrenResolve.UP) {
                for (const item of items) {
                    if (isNil(item.children) || item.children.length <= 0) {
                        continue;
                    } else {
                        const nchildren = [...item.children].map((c) => {
                            c.parent = item.parent;
                            return item;
                        });
                        //子级评论的提升
                        this.repository.save(nchildren);
                    }
                }
            }
        } else {
            items = await this.repository.find({
                where: { id: In(ids) as any },
                withDeleted: this.enableTrash ? true : undefined,
            });
        }
        if (this.enableTrash && trash) {
            const AlreadyDelete = items.map((item) => {
                if (!isNil(item.deletedAt)) {
                    return item;
                }
            });

            const YetDelete = items.filter((item) => isNil(item.deletedAt));
            return [
                ...(await this.repository.remove(AlreadyDelete)),
                ...(await this.repository.softRemove(YetDelete)),
            ];

            return this.repository.remove(items);
        }
    }

    async restore(ids: string[]) {
        if (!this.enableTrash) {
            throw new ForbiddenException(
                `Can not to retore ${this.repository.qbName},because trash not enabled!`,
            );
        }
        const items = await this.repository.find({
            where: { id: In(ids) as any },
            withDeleted: true,
        });
        const trasheds = items.filter((item) => !isNil(item));
        if (trasheds.length < 0) return [];
        await this.repository.restore(trasheds.map((item) => item.id));
        const qb = await this.buildListQB(
            this.repository.buildBaseQB(),
            undefined,
            async (builder) => builder.andWhereInIds(trasheds),
        );
        return qb.getMany();
    }

}
